package middleware

import (
	"EinoService/utils"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"math"
	"sort"
	"strconv"
	"strings"
	"time"
)

type Receive struct {
	MerchantId int    `json:"merchant_id"`
	Body       string `json:"body"`
}

type ReceiveDev struct {
	MerchantId int            `json:"merchant_id"`
	Body       map[string]any `json:"body"`
}

func getPostDatum[T Receive | ReceiveDev](ctx *gin.Context, data *T, field string) error {
	body := ctx.PostForm(field)
	if body != "" {
		return json.Unmarshal([]byte(body), data)
	}
	return errors.New(fmt.Sprintf("empty %s", field))
}

func OpenCheck(ctx *gin.Context) {
	data := make(map[string]interface{})
	config := utils.GetConfig()
	utils.SetClientIP(ctx.ClientIP())
	// 开发模式时，不加密
	var merchantId int

	if config.Verification.Dev {
		var input ReceiveDev
		// 优先匹配表单请求
		if err := getPostDatum(ctx, &input, "data"); err != nil {
			if err := ctx.ShouldBindJSON(&input); err != nil {
				utils.ResponseJson(ctx, 500, "获取数据不匹配_dev")
				ctx.Abort()
				return
			}
		}
		if config.Verification.Debug {
			fmt.Println("接收到的数据：", input)
		}
		merchantId = input.MerchantId
		data = input.Body
	} else {
		var input Receive
		if err := getPostDatum(ctx, &input, "data"); err != nil {
			if err := ctx.ShouldBindJSON(&input); err != nil {
				utils.ResponseJson(ctx, 500, "获取数据不匹配")
				ctx.Abort()
				return
			}
		}
		if config.Verification.Debug {
			fmt.Println("接收到的数据：", input)
		}
		aes, err := utils.NewAes(config.Verification.AesSecret, config.Verification.AesIv)
		if err != nil {
			ctx.JSON(200, gin.H{
				"code": 500,
				"msg":  fmt.Sprintf("获取数据不匹配: %v", err.Error()),
				"data": data,
			})
			ctx.Abort()
			return
		}
		merchantId = input.MerchantId
		strByte, _ := base64.StdEncoding.DecodeString(input.Body)
		byteStr, _ := aes.Decrypt(strByte)
		err = json.Unmarshal(byteStr, &data)
		if err != nil {
			utils.ResponseError(ctx, err)
			ctx.Abort()
			return
		}
	}
	if !utils.InMap(merchantId, config.Verification.MerchantList) {
		utils.ResponseJson(ctx, 500, "商户号不匹配")
		ctx.Abort()
		return
	}
	// 验证签名
	if config.Verification.SignCheck {
		if ctx.Request.RequestURI == "/open/Business/notify" {
			if err := utils.CheckSign(data, config.Verification, false); err != nil {
				utils.ResponseError(ctx, err)
				ctx.Abort()
				return
			}
		} else {
			if err := checkSignature(data); err != nil {
				utils.ResponseError(ctx, err)
				ctx.Abort()
				return
			}
		}
	}
	ctx.Set("body", data)
	ctx.Next()
}

func checkSignature(data map[string]any) error {
	if _, ok := data["sign"]; !ok {
		return errors.New("签名信息不存在")
	}
	cfg := utils.GetConfig().Verification
	data["secret"] = cfg.SignSecret
	timestamp := 0
	if v, ok := data["timestamp"]; ok {
		t := fmt.Sprintf("%T", v)
		switch t {
		case "string":
			timestamp, _ = strconv.Atoi(v.(string))
			break
		case "float64":
			timestamp = int(math.Floor(v.(float64)))
			break
		case "int64":
			timestamp = int(v.(int64))
		}
		now := time.Now().Unix()
		if cfg.TimeCheck && (timestamp > int(now)+300 || timestamp < int(now)-300) {
			return errors.New("传入的时间与当前时间相差超出")
		}
	}
	signData := make(map[string]string)
	joinSignFields := []string{"nonce", "timestamp", "inner_batch_no", "project_id", "money", "mobile", "card_no", "card_name", "inner_invoice_no", "secret", "file_url", "sign_time", "file_hash", "notify_url"}
	var keys []string
	var salary float64
	for f, it := range data {
		if !utils.InMap(f, joinSignFields) {
			continue
		}
		if f == "notify_url" && it == "" {
			continue
		}
		t := fmt.Sprintf("%T", it)
		if f == "money" {
			switch t {
			case "string":
				salary, _ = strconv.ParseFloat(it.(string), 64)
				break
			case "float64":
				salary = it.(float64)
				break
			case "float32":
				salary = float64(it.(float32))
				break
			default:
				return errors.New(fmt.Sprintf("%s格式不正确", f))
			}
			if salary < cfg.MinValueLimit {
				return errors.New(fmt.Sprintf("%s不能小于%f", f, cfg.MinValueLimit))
			}
		}
		_value := ""
		var err error
		if f == "timestamp" {
			_value = strconv.Itoa(timestamp)
		} else {
			_value, err = utils.ConvAnyToString(it)
			if err != nil {
				fmt.Println("转换字符串失败：", f, " =", err.Error())
				continue
			}
		}
		signData[f] = fmt.Sprintf("%v", _value)
		keys = append(keys, f)
	}
	sort.Strings(keys)
	signList := make([]string, 0)
	for _, k := range keys {
		value := signData[k]
		_value, err := utils.ConvAnyToString(value)
		if err != nil {
			fmt.Println("转换字符串失败：", k, " =", err.Error())
			continue
		}
		signList = append(signList, fmt.Sprintf("%s=%v", k, _value))
	}
	delete(data, "secret")
	if cfg.Debug {
		fmt.Println("签名参数：", signList)
	}
	sign := fmt.Sprintf("%x", sha1.Sum([]byte(strings.Join(signList, "&"))))
	if sign != data["sign"] {
		return errors.New("验证签名失败" + utils.Ternary(cfg.Debug, fmt.Sprintf("：传入：%s, 本地：%s", data["sign"], sign), ""))
	}
	return nil
}
